/*
Copyright 2024 MySQL Parser Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqlparser

// Visit defines the signature of a function that can be used to visit all nodes of a parse tree.
// The function should return true to continue visiting child nodes, or false to stop.
// If it returns an error, walking is interrupted, and the error is returned.
type Visit func(node SQLNode) (kontinue bool, err error)

// Walk calls visit on every node in the tree.
// If visit returns true, the underlying nodes are also visited.
// If it returns an error, walking is interrupted, and the error is returned.
func Walk(visit Visit, nodes ...SQLNode) error {
	for _, node := range nodes {
		if err := VisitSQLNode(node, visit); err != nil {
			return err
		}
	}
	return nil
}

// VisitSQLNode visits a SQLNode and its children
func VisitSQLNode(node SQLNode, visit Visit) error {
	if node == nil {
		return nil
	}

	// Call the visit function on the current node
	kontinue, err := visit(node)
	if err != nil || !kontinue {
		return err
	}

	// Visit children based on node type
	switch n := node.(type) {
	case *Select:
		return visitSelect(n, visit)
	case *Insert:
		return visitInsert(n, visit)
	case *Update:
		return visitUpdate(n, visit)
	case *Delete:
		return visitDelete(n, visit)
	case *CreateTableStatement:
		return visitCreateTable(n, visit)
	case *AlterTableStatement:
		return visitAlterTable(n, visit)
	case *DropTableStatement:
		return visitDropTable(n, visit)
	case *CreateIndexStatement:
		return visitCreateIndex(n, visit)
	case *DropIndexStatement:
		return visitDropIndex(n, visit)
	case *TruncateStatement:
		return visitTruncate(n, visit)
	case *CreateViewStatement:
		return visitCreateView(n, visit)
	case *DropViewStatement:
		return visitDropView(n, visit)
	case *AlterViewStatement:
		return visitAlterView(n, visit)
	case *ColName:
		return visitColName(n, visit)
	case *Literal:
		return nil // Literals have no children
	case *NullVal:
		return nil // NullVal has no children
	case *AliasedTableExpr:
		return visitAliasedTableExpr(n, visit)
	case *AliasedExpr:
		return visitAliasedExpr(n, visit)
	case *StarExpr:
		return visitStarExpr(n, visit)
	case *Where:
		return visitWhere(n, visit)
	case *Limit:
		return visitLimit(n, visit)
	case *Order:
		return visitOrder(n, visit)
	case *UpdateExpr:
		return visitUpdateExpr(n, visit)
	case *Subquery:
		return visitSubquery(n, visit)
	case *JoinTableExpr:
		return visitJoinTableExpr(n, visit)
	case *OnCondition:
		return visitOnCondition(n, visit)
	case *UsingCondition:
		return visitUsingCondition(n, visit)
	case *FuncExpr:
		return visitFuncExpr(n, visit)
	case *BinaryExpr:
		return visitBinaryExpr(n, visit)
	case SelectExprs:
		return visitSelectExprs(n, visit)
	case TableExprs:
		return visitTableExprs(n, visit)
	case TableNames:
		return visitTableNames(n, visit)
	case Columns:
		return visitColumns(n, visit)
	case GroupBy:
		return visitGroupBy(n, visit)
	case OrderBy:
		return visitOrderBy(n, visit)
	case UpdateExprs:
		return visitUpdateExprs(n, visit)

	default:
		// For unknown types, just return nil
		return nil
	}
}

// Visit functions for each node type

func visitSelect(node *Select, visit Visit) error {
	if err := visitSelectExprs(node.SelectExprs, visit); err != nil {
		return err
	}
	if err := visitTableExprs(node.From, visit); err != nil {
		return err
	}
	if node.Where != nil {
		if err := VisitSQLNode(node.Where, visit); err != nil {
			return err
		}
	}
	if err := visitGroupBy(node.GroupBy, visit); err != nil {
		return err
	}
	if node.Having != nil {
		if err := VisitSQLNode(node.Having, visit); err != nil {
			return err
		}
	}
	if err := visitOrderBy(node.OrderBy, visit); err != nil {
		return err
	}
	if node.Limit != nil {
		if err := VisitSQLNode(node.Limit, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitInsert(node *Insert, visit Visit) error {
	if err := visitColumns(node.Columns, visit); err != nil {
		return err
	}
	if node.Rows != nil {
		if err := VisitSQLNode(node.Rows, visit); err != nil {
			return err
		}
	}
	if err := visitOnDup(node.OnDup, visit); err != nil {
		return err
	}
	return nil
}

func visitUpdate(node *Update, visit Visit) error {
	if err := visitTableExprs(node.TableExprs, visit); err != nil {
		return err
	}
	if err := visitUpdateExprs(node.Exprs, visit); err != nil {
		return err
	}
	if node.Where != nil {
		if err := VisitSQLNode(node.Where, visit); err != nil {
			return err
		}
	}
	if err := visitOrderBy(node.OrderBy, visit); err != nil {
		return err
	}
	if node.Limit != nil {
		if err := VisitSQLNode(node.Limit, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitDelete(node *Delete, visit Visit) error {
	if err := visitTableNames(node.Targets, visit); err != nil {
		return err
	}
	if err := visitTableExprs(node.TableExprs, visit); err != nil {
		return err
	}
	if node.Where != nil {
		if err := VisitSQLNode(node.Where, visit); err != nil {
			return err
		}
	}
	if err := visitOrderBy(node.OrderBy, visit); err != nil {
		return err
	}
	if node.Limit != nil {
		if err := VisitSQLNode(node.Limit, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitCreateTable(node *CreateTableStatement, visit Visit) error {
	for _, col := range node.Columns {
		if col.Name != nil {
			if err := VisitSQLNode(col.Name, visit); err != nil {
				return err
			}
		}
		if col.Default != nil {
			if err := VisitSQLNode(col.Default, visit); err != nil {
				return err
			}
		}
	}
	for _, constraint := range node.Constraints {
		for _, col := range constraint.Columns {
			if err := VisitSQLNode(col, visit); err != nil {
				return err
			}
		}
		if constraint.CheckExpr != nil {
			if err := VisitSQLNode(constraint.CheckExpr, visit); err != nil {
				return err
			}
		}
		if constraint.References != nil {
			for _, col := range constraint.References.Columns {
				if err := VisitSQLNode(col, visit); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

func visitAlterTable(node *AlterTableStatement, visit Visit) error {
	for _, action := range node.Actions {
		if action.Column != nil && action.Column.Name != nil {
			if err := VisitSQLNode(action.Column.Name, visit); err != nil {
				return err
			}
			if action.Column.Default != nil {
				if err := VisitSQLNode(action.Column.Default, visit); err != nil {
					return err
				}
			}
		}
		if action.OldName != nil {
			if err := VisitSQLNode(action.OldName, visit); err != nil {
				return err
			}
		}
		if action.NewName != nil {
			if err := VisitSQLNode(action.NewName, visit); err != nil {
				return err
			}
		}
		if action.After != nil {
			if err := VisitSQLNode(action.After, visit); err != nil {
				return err
			}
		}
	}
	return nil
}

func visitDropTable(node *DropTableStatement, visit Visit) error {
	for _, table := range node.Tables {
		if err := VisitSQLNode(table, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitCreateIndex(node *CreateIndexStatement, visit Visit) error {
	for _, col := range node.Columns {
		if err := VisitSQLNode(col, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitDropIndex(node *DropIndexStatement, visit Visit) error {
	return nil
}

func visitTruncate(node *TruncateStatement, visit Visit) error {
	return nil
}

func visitCreateView(node *CreateViewStatement, visit Visit) error {
	if err := visitColumns(node.Columns, visit); err != nil {
		return err
	}
	if node.Select != nil {
		return VisitSQLNode(node.Select, visit)
	}
	return nil
}

func visitDropView(node *DropViewStatement, visit Visit) error {
	for _, view := range node.Views {
		if err := VisitSQLNode(view, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitAlterView(node *AlterViewStatement, visit Visit) error {
	if err := visitColumns(node.Columns, visit); err != nil {
		return err
	}
	if node.Select != nil {
		return VisitSQLNode(node.Select, visit)
	}
	return nil
}

func visitColName(node *ColName, visit Visit) error {
	return nil
}

func visitAliasedTableExpr(node *AliasedTableExpr, visit Visit) error {
	if node.Expr != nil {
		return VisitSQLNode(node.Expr, visit)
	}
	return nil
}

func visitAliasedExpr(node *AliasedExpr, visit Visit) error {
	if node.Expr != nil {
		return VisitSQLNode(node.Expr, visit)
	}
	return nil
}

func visitStarExpr(node *StarExpr, visit Visit) error {
	return nil
}

func visitWhere(node *Where, visit Visit) error {
	if node.Expr != nil {
		return VisitSQLNode(node.Expr, visit)
	}
	return nil
}

func visitLimit(node *Limit, visit Visit) error {
	if node.Offset != nil {
		if err := VisitSQLNode(node.Offset, visit); err != nil {
			return err
		}
	}
	if node.Rowcount != nil {
		return VisitSQLNode(node.Rowcount, visit)
	}
	return nil
}

func visitOrder(node *Order, visit Visit) error {
	if node.Expr != nil {
		return VisitSQLNode(node.Expr, visit)
	}
	return nil
}

func visitUpdateExpr(node *UpdateExpr, visit Visit) error {
	if node.Name != nil {
		if err := VisitSQLNode(node.Name, visit); err != nil {
			return err
		}
	}
	if node.Expr != nil {
		return VisitSQLNode(node.Expr, visit)
	}
	return nil
}

// Visit functions for slice types

func visitSelectExprs(nodes SelectExprs, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(node, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitTableExprs(nodes TableExprs, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(node, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitTableNames(nodes TableNames, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(&node, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitColumns(nodes Columns, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(&node, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitGroupBy(nodes GroupBy, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(node, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitOrderBy(nodes OrderBy, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(node, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitUpdateExprs(nodes UpdateExprs, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(node, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitPartitions(nodes Partitions, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(&node, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitOnDup(nodes OnDup, visit Visit) error {
	for _, node := range nodes {
		if err := VisitSQLNode(node, visit); err != nil {
			return err
		}
	}
	return nil
}

// Visit functions for new node types

func visitSubquery(node *Subquery, visit Visit) error {
	if node.Select != nil {
		return VisitSQLNode(node.Select, visit)
	}
	return nil
}

func visitJoinTableExpr(node *JoinTableExpr, visit Visit) error {
	if node.LeftExpr != nil {
		if err := VisitSQLNode(node.LeftExpr, visit); err != nil {
			return err
		}
	}
	if node.RightExpr != nil {
		if err := VisitSQLNode(node.RightExpr, visit); err != nil {
			return err
		}
	}
	if node.Condition != nil {
		return VisitSQLNode(node.Condition, visit)
	}
	return nil
}

func visitOnCondition(node *OnCondition, visit Visit) error {
	if node.Expr != nil {
		return VisitSQLNode(node.Expr, visit)
	}
	return nil
}

func visitUsingCondition(node *UsingCondition, visit Visit) error {
	return visitColumns(node.Cols, visit)
}

func visitFuncExpr(node *FuncExpr, visit Visit) error {
	for _, arg := range node.Exprs {
		if err := VisitSQLNode(arg, visit); err != nil {
			return err
		}
	}
	return nil
}

func visitBinaryExpr(node *BinaryExpr, visit Visit) error {
	if node.Left != nil {
		if err := VisitSQLNode(node.Left, visit); err != nil {
			return err
		}
	}
	if node.Right != nil {
		if err := VisitSQLNode(node.Right, visit); err != nil {
			return err
		}
	}
	return nil
}